[TRACK_B] Route the last pure builtins — three defects, a false doc promise, and the first conditional prelude - #75
Draft
ib823 wants to merge 4 commits into
Draft
[TRACK_B] Route the last pure builtins — three defects, a false doc promise, and the first conditional prelude#75ib823 wants to merge 4 commits into
ib823 wants to merge 4 commits into
Conversation
…cts they hid
REQ-70. I described the remaining interpreter-only set as a backlog of builtins
nobody had examined. Examining them found that most were not missing
implementations at all -- they were defects that the Backend column happened to
render as "interp-only".
1. A LANGUAGE FEATURE DID NOT COMPILE. adalah_kiri/adalah_kanan/nilai_kiri/
nilai_kanan are not stdlib functions a user calls; they are COMPILER
INTERNALS. The parser's if-chain pattern compiler emits them for a
constructor pattern nested where a `Case` cannot go -- `(Ada(a), Tiada)`
inside a tuple pattern. Unrouted, that entire class of pattern ran under
`riinac run` and failed `riinac build` with
`Codegen Error: unbound variable: nilai_kiri`. Four obscure interp-only rows
were in fact one missing feature in the compiled backend.
2. baki/rem WERE UNCALLABLE FROM ANY WELL-TYPED PROGRAM. They are binary and
take a pair -- the interpreter's extract_pair_ints and the emitted C's
RIINA_TAG_PAIR check both say so -- but the typechecker declared them
`Nombor -> Nombor`, because they shared a registration loop with the
genuinely unary log2. So `baki(10, 3)` failed with "Expected function type,
found Int" and `baki((10, 3))` failed with "expected Int, found
Prod(Int, Int)". Two of three components agreed; the type was the outlier,
so the type is what changed.
3. cetak_baris WAS A PURE ALIASING GAP. The interpreter binds cetakln, println
AND cetak_baris to one Value::Builtin("cetakln"). Only builtin_canonical knew
two of the three, so a program using the third ran and then failed to build.
baki, log2 and rawak already HAD C implementations sitting unreferenced in
emit.rs, exactly like the json helpers before them. Only the gate was missing.
ROUTED (12 names): the four sum helpers, baki/rem, log2, julat/julat_inklusif,
rawak/random, cetak_baris. New C for the ranges and the sum helpers; the ranges
mirror the interpreter's SATURATING inclusive increment, and the sum payload
projections are deliberately lenient on a non-sum argument as the interpreter is.
rawak/random is routed despite the two backends being uncomparable by output --
both are time-seeded. The differential pins the range invariant instead and says
plainly that equality is not being checked. Routing is still right: without it a
compiled RIINA program has no source of randomness at all. Neither
implementation is a CSPRNG and neither claims to be.
THE BACKEND COLUMN WAS MAKING A FALSE PROMISE, now fixed. Its `interp-only` cell
reads "`riinac run` only". For the eight crypto-agility builtins
(guna_kripto/use_crypto, pilih_algo/select_algorithm, cipher/sifer,
hash_dengan/hash_with) that was FALSE: they are in the typechecker registry with
`Fn(Teks, Any, Kripto)` and carry the REQ-48 deprecation check at their call
sites, but NO runtime binds them, so `riinac run` fails with `unbound variable`
exactly as `riinac build` does. Verified by command for all eight. A three-state
column had no way to say "typed but unimplemented", so it said the nearest thing,
which was wrong. Added a fourth state `typed-only`, derived from a new
`riina_codegen::interpreter_supports_builtin` that builds the real interpreter
environment and looks the name up -- not from a list, which would drift the
moment a runtime is added.
Counts re-derived from the regenerated doc: 373 registered -- compiled 21 /
native-only 314 / interp-only 30 / typed-only 8. The remaining 30 are the TLS
half (16), the VirtualFs trio (6), csrf_generate (2) and the Unicode NFC and
confusables builtins (6); the first three are excluded for stated reasons, and
the Unicode six are the next increment.
Tests: pure_builtin_differential.rs (6 cases). The load-bearing one is the
nested constructor pattern, ordered so a failing tag test precedes a succeeding
one, plus a separate case asserting the BOUND PAYLOAD matches -- a nilai_kiri
returning the sum rather than its contents would still pick the right arm and
print the wrong number.
Verified: 03_PROTO 3366/0 (+6), clippy clean on both workspaces, audit-docs.sh
0 discrepancies.
… conditional prelude REQ-70. Routes nfc/ke_nfc (UAX #15), skeleton/rangka and adalah_keliru/is_confusable (UTS #39) -- the last group with no stated reason to stay interpreter-only. 341 of 373 builtins now compile. The Gate C sample app 07_EXAMPLES/03_applications/keselamatan_nama.rii NOW COMPILES. It was one of four apps the gate cites as shipping evidence and the only one that would not build; verified by command, and its compiled output is byte-identical to `riinac run`. WHY THIS NEEDED A CONDITIONAL BLOCK. Every other builtin's C is a few dozen lines. These need ~250 KB of vendored UCD tables. The emitted prelude is already ~228 KB for a hello-world and is otherwise entirely unconditional, so emitting the tables always would MORE THAN DOUBLE every compiled binary to serve three builtins most programs never call. emit() now scans the IR for a call to one of the three and emits the block only then. Measured: hello-world stays 229,600 bytes with zero tables; a program calling nfc gets 492,515. THE TABLES ARE GENERATED, NOT TRANSCRIBED. The C arrays are written out from the same unicode_nfc_data / unicode_confusables_data statics the interpreter reads. A hand-copied 250 KB table is a drift source no differential could realistically cover; generating it means the two backends cannot disagree about the DATA at all -- only about the ALGORITHM, which is ~150 lines and is what the tests actually exercise. A NEGATIVE CONTROL CAUGHT A TEST THAT PASSED FOR THE WRONG REASON. The blocking case was first written as `a` + dot-below + acute, on the reasoning that the acute cannot reach the starter past a lower-class mark. Deleting the blocking condition from the emitted C did NOT make that test fail: there both marks compose into the starter in turn, so blocking never applies. Rewritten as `a` + U+0305 overline + U+0301 acute -- equal combining classes, and the overline does not compose -- where removing the check composes `a`+acute across the overline and shortens the result from five bytes to four. Re-ran the control: the test now fails as intended. Recorded in the test's own comment so the input is not "simplified" back later. Cases chosen where the ALGORITHM decides rather than the data: Hangul (composed and decomposed arithmetically, so it appears in no table -- a lookup-only port leaves it decomposed); stable canonical ordering; the blocking rule above; and that skeleton ends in NFD, not NFC, because it is a comparison key rather than a display form. Plus hello_world_carries_no_ucd_tables, which keeps the conditional-emission promise honest -- without it the block would silently become unconditional again and nothing else would notice, the binaries would just get bigger. Every remaining interp-only builtin now has a stated reason: the TLS half (16, awaiting the Law 8 decision), the VirtualFs trio (6, needs an in-memory FS and quota in C), and csrf_generate (2, non-deterministic). Counts re-derived from the regenerated doc: compiled 21 / native-only 320 / interp-only 24 / typed-only 8. Verified: 03_PROTO 3373/0 (+7), 05_TOOLING 323/0, clippy clean on both, audit-docs.sh 0 discrepancies.
…found Updates the REQ-70 row and the Part 12 Wave 1.0 status. Counts re-derived from the regenerated docs/api/STDLIB.md: 373 registered, 341 compile -- compiled 21 / native-only 320 / interp-only 24 / typed-only 8. Corrects my own framing. I listed these 26 as unexamined backlog and told the owner the remaining interpreter-only set each had a stated reason. Both were wrong: only 24 of the 50 did, and examining the other 26 found three defects rather than missing implementations -- a language feature (nested constructor patterns) that did not compile, a pair-taking builtin declared unary and so uncallable from any well-typed program, and an aliasing gap. Also records that the Backend column was making a false promise for eight crypto-agility builtins, now fixed with a fourth `typed-only` state derived from the real interpreter environment; that the Unicode three needed the emitter's first conditional prelude block, with the measured sizes; and that keselamatan_nama.rii -- one of four Gate C sample apps, and the only one that would not build -- now compiles. Generalises the wave's recurring finding one step further. It already said a family marked as lowering is not a family that agrees. Two cases in this wave went further: they PASSED while the property they named was absent from the code. json's nine differential cases all fed well-formed input to a parser that could not fail, and the Unicode composition-blocking case chose an input where blocking never applies. Both were found by deliberately breaking the implementation and checking whether the test noticed. A green differential is evidence only about the inputs it actually runs.
…ing (3360 -> 3373 tests)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
REQ-70. Every builtin family is now routed: 341 of 373 compile, up from 323.
I described these 26 as unexamined backlog. That framing was wrong, and so was my earlier claim that the remaining interpreter-only set each had a stated reason — only 24 of 50 did. Examining the other 26 found three defects rather than missing implementations.
Three defects, not a backlog
A language feature did not compile.
adalah_kiri/adalah_kanan/nilai_kiri/nilai_kananare not stdlib functions a user calls — they are compiler internals. The parser's if-chain pattern compiler emits them for a constructor pattern nested where aCasecannot go, such as(Ada(a), Tiada)inside a tuple pattern. Unrouted, that entire class of pattern ran underriinac runand failedriinac buildwithCodegen Error: unbound variable: nilai_kiri. Four obscureinterp-onlyrows were in fact one missing feature in the compiled backend.baki/remwere uncallable from any well-typed program. They are binary and take a pair — the interpreter'sextract_pair_intsand the emitted C'sRIINA_TAG_PAIRcheck both say so — but the typechecker declared themNombor -> Nombor, because they shared a registration loop with the genuinely unarylog2. Sobaki(10, 3)failed with "Expected function type, found Int" andbaki((10, 3))failed with "expected Int, found Prod(Int, Int)". Two of three components agreed; the type was the outlier, so the type is what changed.cetak_bariswas a pure aliasing gap. The interpreter bindscetakln,printlnandcetak_baristo oneValue::Builtin("cetakln"). Onlybuiltin_canonicalknew two of the three.baki,log2andrawakalready had C sitting unreferenced inemit.rs, exactly like the json helpers before them.The Backend column was making a false promise
interp-onlyreads "riinac runonly". For the eight crypto-agility builtins (guna_kripto/use_crypto,pilih_algo/select_algorithm,cipher/sifer,hash_dengan/hash_with) that was false: they sit in the typechecker registry withFn(Teks, Any, Kripto)and carry the REQ-48 deprecation check at their call sites, but no runtime binds them. Verified by command for all eight —riinac runfails withunbound variableexactly asriinac builddoes.A three-state column had no way to say "typed but unimplemented", so it said the nearest thing. Added a fourth state
typed-only, derived from a newriina_codegen::interpreter_supports_builtinthat builds the real interpreter environment and looks the name up — not from a list, which would drift the moment a runtime is added.The Unicode three needed the emitter's first conditional prelude block
nfc,skeletonandadalah_keliruneed ~250 KB of vendored UCD tables. The prelude is already ~228 KB for a hello-world and is otherwise entirely unconditional, so emitting the tables always would more than double every compiled binary to serve three builtins most programs never call.emit()now scans the IR and emits the block only on a call.Measured: hello-world stays 229,600 bytes with zero tables; a program calling
nfcgets 492,515.The C tables are generated from the same Rust statics the interpreter reads, not transcribed. A hand-copied 250 KB table is a drift source no differential could realistically cover; generating it means the backends cannot disagree about the data at all — only about the ~150-line algorithm, which is what the tests exercise.
A negative control caught a test passing for the wrong reason
The composition-blocking case was first written as
a+ dot-below + acute. Deleting the blocking condition from the emitted C did not make it fail — there both marks compose into the starter in turn, so blocking never applies.Rewritten as
a+ U+0305 overline + U+0301 acute: equal combining classes, and the overline does not compose, so removing the check composesa+acute across the overline and shortens the result from five bytes to four. Re-ran the control; the test then failed as intended. The reasoning is recorded in the test's own comment so the input is not "simplified" back later.keselamatan_nama.riinow compilesOne of the four Gate C sample apps, and the only one that would not build. Verified by command, with output byte-identical to
riinac run.What remains, and why
Every remaining
interp-onlybuiltin has a stated reason:jaring_tls_*/net_tls_*csrf_generateThe 8
typed-onlycrypto-agility builtins need a runtime, not routing — separate work, and not REQ-70's to close.Tests
pure_builtin_differential.rs— 6 cases. The load-bearing one is the nested constructor pattern, ordered so a failing tag test precedes a succeeding one, plus a separate case asserting the bound payload matches (anilai_kirireturning the sum rather than its contents would still pick the right arm and print the wrong number).unicode_differential.rs— 7 cases, chosen where the algorithm decides rather than the data: Hangul (arithmetic, in no table), stable canonical ordering, the blocking rule above, and thatskeletonends in NFD rather than NFC. Plushello_world_carries_no_ucd_tables, which keeps the conditional-emission promise honest — without it the block would silently become unconditional again and nothing else would notice.rawak/randomis routed despite the backends being uncomparable by output (both time-seeded). The differential pins the range invariant and says plainly that equality is not checked. Routing is still right: without it a compiled RIINA program has no source of randomness at all. Neither implementation is a CSPRNG and neither claims to be.Verification
cargo clippy -- -D warnings: clean on both workspacesscripts/audit-docs.sh: 0 discrepancies (2 pre-existing warnings, unrelated)docs/api/STDLIB.mdregenerated from the compiler — counts re-derived, not carried forward🤖 Generated with Claude Code
https://claude.ai/code/session_01Uu28z8CdRQ1SLzTv8yszth
Generated by Claude Code